import { findIndex } from "lodash-es";
import { NodePath } from "@babel/traverse";
import { types as t, traverse } from "@babel/core";
import { scriptAstUtil } from "../util";

const lifeCycleMap: Record<string, string> = {
    onReady: 'mounted'
};

const ignoreMethods = [
    'onLoad',
    'onShow',
    'onHide',
    'onUnload',
    'onPullDownRefresh',
    'onReachBottom',
    'onShareAppMessage',
    'onPageScroll',
    'onTabItemTap'
];

const unsupprotedWxApis = [
    'getRecorderManager',
    'createInnerAudioContext',
    'createAudioContext',
    'createCameraContext',
    'createLivePlayerContext',
    'createLivePusherContext',
    'canIUse',
    'startBeaconDiscovery',
    'stopBeaconDiscovery',
    'getBeacons',
    'onBeaconUpdate',
    'onBeaconServiceChange',
    'getHCEState',
    'startHCE',
    'stopHCE',
    'onHCEMessage',
    'sendHCEMessage',
    'startWifi',
    'stopWifi',
    'connectWifi',
    'getWifiList',
    'onGetWifiList',
    'setWifiList',
    'onWifiConnected',
    'getConnectedWifi',
    'showModal',
    'setTopBarText',
    'reLaunch',
    'createAnimation',
    'createSelectorQuery',
    'stopPullDownRefresh',
    'checkSession',
    'login',
    'authorize'
];

export declare namespace WxScript {
    interface Config {
        [name: string]: any;
    }
}

export class WxScript {
    node: any;
    props: any;

    constructor(public source: string, public isApp?: Boolean | undefined) {
        this.initNode();
    }

    get config() {
        let props = this.props;
        for (let i = 0; i < props.length; i++) {
            let keyName = props[i].key.name;
            if (keyName === 'config') {
                let result = eval(`({${scriptAstUtil.transformFromNode(props[i])}})`);
                return result.config;
            }
        }
    }

    initNode() {
        this.node = scriptAstUtil.transform(this.source);
        this.props = scriptAstUtil.getExportDefaultProperties(this.node);
    }
    generator() {
        this.transformProperties();
        this.transformWx();
        if (this.isApp) {
            this.addOnLaunchToApp();
        }
        return this.generateCode();
    }

    transformProperties() {
        let props = this.props;
        let methods = {
            type: 'ObjectProperty',
            key: {
                type: 'Identifier',
                name: 'methods'
            },
            value: {
                type: 'ObjectExpression',
                properties: []
            }
        };
        props.push(methods);
        for (let i = props.length - 1; i >= 0; i--) {
            let type = props[i].type;
            let keyName = props[i].key.name;
            if (type === 'ObjectProperty' && keyName === 'data') {
                this._tranformData(props, props[i], i);
            }
            if (type === 'ObjectMethod') {
                if (keyName === 'onReady') {
                    this._transformLifeCycle(props[i], keyName);
                }
                else if (ignoreMethods.indexOf(keyName) > -1) {
                    props.splice(i, 1);
                }
                else {
                    this._transformMethods(props, methods, i);
                }
            }
        }
    }

    transformWx() {
        let visitor = {
            Identifier: (path: any) => {
                if (path.node.name === 'wx') {
                    path.node.name = 'ui';
                }
            },
            ExpressionStatement: (path: NodePath<t.ExpressionStatement>) => {
                this.vistorExpressionStatement(path);
            }
        };
        traverse(this.node, visitor);
    }

    vistorExpressionStatement(path: NodePath<t.ExpressionStatement>) {
        let n = path.node;
        let isSetData = t.isCallExpression(n.expression)
            && t.isMemberExpression(n.expression.callee)
            && n.expression.callee.object.type === 'ThisExpression'
            && t.isIdentifier(n.expression.callee.property, { name: 'setData' });
        if (isSetData) {
            if (t.isBlockStatement(path.parent)) {
                let body = path.parent.body;
                // @ts-ignore
                let properties = n['expression']['arguments'][0].properties;
                for (let i = body.length; i >= 0; i--) {
                    if (body[i] === n) {
                        let statements = properties.map((prop: any) => {
                            return {
                                type: 'ExpressionStatement',
                                expression: {
                                    type: 'AssignmentExpression',
                                    operator: '=',
                                    left: {
                                        type: 'MemberExpression',
                                        object: {
                                            type: 'ThisExpression'
                                        },
                                        property: {
                                            type: 'Identifier',
                                            name: prop.key.name
                                        }
                                    },
                                    right: prop.value
                                }
                            };
                        });
                        body.splice(i, 1, ...statements);
                    }
                }
            }
        }
        if (scriptAstUtil.isMatchMethods(n, unsupprotedWxApis)) {
            // @ts-ignore
            let id = n.expression['callee'].object.name;
            // @ts-ignore
            let prop = n.expression['callee'].property.name;
            if (id === 'wx') {
                id = 'ui';
            }
            if (t.isBlockStatement(path.parent)) {
                let body = path.parent.body;
                let index = findIndex(body, item => item === n);
                const comment: t.Comment = {
                    type: 'CommentBlock',
                    value: `* WX Tip:Touch UI不支持此API \n * ${id}.${prop}()\n `
                };
                let prevNode = body[index - 1];
                let nextNode = body[index + 1];
                if (index === 0) {
                    path.parent.innerComments = path.parent.innerComments || [];
                    path.parent.innerComments.push(comment);
                }
                else if (prevNode && !scriptAstUtil.isMatchMethods(prevNode, unsupprotedWxApis)) {
                    prevNode.trailingComments = prevNode.trailingComments || [];
                    prevNode.trailingComments.push(comment);
                }
                else if (nextNode && !scriptAstUtil.isMatchMethods(nextNode, unsupprotedWxApis)) {
                    nextNode.leadingComments = nextNode.leadingComments || [];
                    nextNode.leadingComments.push(comment);
                }
                body.splice(index, 1);
            }
        }
    }

    addOnLaunchToApp() {
        this.node.program.body.unshift({
            type: 'ImportDeclaration',
            specifiers: [{
                type: 'ImportDefaultSpecifier',
                local: {
                    type: 'Identifier',
                    name: 'system'
                }
            }],
            importKind: 'value',
            source: {
                type: 'StringLiteral',
                value: './static/utils/system'
            }
        });
        this.props.push({
            type: 'ObjectMethod',
            method: true,
            key: {
                type: 'Identifier',
                name: 'onLaunch'
            },
            kind: 'method',
            params: [],
            body: {
                type: 'BlockStatement',
                body: [{
                    type: 'ExpressionStatement',
                    expression: {
                        type: 'CallExpression',
                        callee: {
                            type: 'MemberExpression',
                            object: {
                                type: 'Identifier',
                                name: 'system'
                            },
                            property: {
                                type: 'Identifier',
                                name: 'attachInfo'
                            }
                        }
                    }
                }]
            }
        });
    }

    _tranformData(props: any, data: any, i: number) {
        let newData = {
            type: 'ObjectMethod',
            key: {
                type: 'Identifier',
                name: 'data'
            },
            computed: false,
            kind: 'method',
            method: true,
            generator: false,
            expression: false,
            async: false,
            params: [],
            body: {
                type: 'BlockStatement',
                body: [{
                    type: 'ReturnStatement',
                    argument: data.value
                }]
            }
        };
        props.splice(i, 1, newData);
    }

    _transformMethods(props: any, methods: any, i: number) {
        methods.value.properties.push(props[i]);
        props.splice(i, 1);
    }

    _transformLifeCycle(lifeCycle: any, name: string) {
        if (lifeCycleMap[name]) {
            lifeCycle.key.name = lifeCycleMap[name];
        }
    }

    generateCode() {
        return scriptAstUtil.transformFromAst(this.node, (code: string) => code.replace(/this\.data/g, 'this')
            .replace(/getApp\(\)/g, 'ui.getApp()')
        );
    }
}
